/*******************************************************************************
 * Copyright (c) 2009 Paul VanderLei, Simon Archer, Jeff McAffer and others. All
 * rights reserved. This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License v1.0 which
 * accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Toast is an Equinox/OSGi system developed for the book Eclipse, Equinox and
 * OSGi - Creating Highly Modular Java Applications See http://equinoxosgi.org
 *
 * Contributors: Paul VanderLei, Simon Archer and Jeff McAffer - initial API and
 * implementation
 *******************************************************************************/
package org.equinoxosgi.toast.core;

import org.equinoxosgi.toast.core.spi.IStatusIdFinder;
import org.equinoxosgi.toast.internal.core.LogProxy;
import org.osgi.service.log.LogService;

public final class LogUtility {
	private static final int DEFAULT_BUFFER_SIZE = 1024; // 1K

	private static final String LOG_LEVEL_DEBUG_VALUE = "DEBUG";
	private static final String LOG_LEVEL_ERROR_VALUE = "ERROR";
	private static final String LOG_LEVEL_INFO_VALUE = "INFO";
	private static final String LOG_LEVEL_WARNING_VALUE = "WARNING";
	private static final String LOG_LEVEL_PROPERTY = "toast.logLevel";
	private static final String DEFAULT_LOG_LEVEL = LOG_LEVEL_DEBUG_VALUE;
	private static final String INITIAL_LOG_LEVEL = System.getProperty(LOG_LEVEL_PROPERTY, DEFAULT_LOG_LEVEL);

	private static final String TRACE_PROPERTY = "toast.trace";
	private static final String DEFAULT_TRACE = Boolean.FALSE.toString();
	private static boolean TRACING = Boolean.valueOf(System.getProperty(TRACE_PROPERTY, DEFAULT_TRACE)).booleanValue();

	private static final LogUtility INSTANCE = new LogUtility();

	private static int getInitialLogLevelValue() {
		if (INITIAL_LOG_LEVEL.equalsIgnoreCase(LOG_LEVEL_ERROR_VALUE)) {
			return LogService.LOG_ERROR;
		}
		if (INITIAL_LOG_LEVEL.equalsIgnoreCase(LOG_LEVEL_WARNING_VALUE)) {
			return LogService.LOG_WARNING;
		}
		if (INITIAL_LOG_LEVEL.equalsIgnoreCase(LOG_LEVEL_INFO_VALUE)) {
			return LogService.LOG_INFO;
		}
		if (INITIAL_LOG_LEVEL.equalsIgnoreCase(LOG_LEVEL_DEBUG_VALUE)) {
			return LogService.LOG_DEBUG;
		}
		return LogService.LOG_DEBUG; // Just in case LOG_LEVEL is invalid.
	}

	/**
	 * Public getter for the <code>LogUtility</code> singleton instance.
	 *
	 * @return The <code>LogUtility</code> singleton instance.
	 */
	public static LogUtility getInstance() {
		return INSTANCE;
	}

	/**
	 * Get the current logging level.
	 *
	 * @return One of the <code>org.osgi.service.log.LogService</code>
	 *         constants.
	 */
	public static int getLoggingLevel() {
		final LogUtility instance = getInstance();
		final int level = instance.getLogLevel();
		return level;
	}

	public static String getStatusId(final Object object) {
		final LogUtility instance = getInstance();
		return instance.findStatusId(object);
	}

	/**
	 * Query the current log level.
	 *
	 * @param level
	 *            The level to compare against.
	 * @return True if the log level is equal or greater than the specified
	 *         level, otherwise false.
	 */
	private static boolean isLogging(final int level) {
		final LogUtility instance = getInstance();
		final int threshold = instance.getLogLevel();
		final boolean logging = threshold >= level;
		return logging;
	}

	/**
	 * Query whether debug messages are being logged.
	 *
	 * @return True if debug message are being logged, otherwise false.
	 */
	public static boolean isLoggingDebug() {
		return isLogging(LogService.LOG_DEBUG);
	}

	/**
	 * Query whether error messages are being logged.
	 *
	 * @return True if error message are being logged, otherwise false.
	 */
	public static boolean isLoggingError() {
		return isLogging(LogService.LOG_ERROR);
	}

	/**
	 * Query whether info messages are being logged.
	 *
	 * @return True if info message are being logged, otherwise false.
	 */
	public static boolean isLoggingInfo() {
		return isLogging(LogService.LOG_INFO);
	}

	/**
	 * Query whether debug messages are being logged.
	 *
	 * @return True if debug message are being logged, otherwise false.
	 */
	public static boolean isLoggingWarning() {
		return isLogging(LogService.LOG_WARNING);
	}

	/**
	 * Query the tracing field. Answers <code>true</code> when tracing in on,
	 * and <code>false</code> when tracing is off.
	 *
	 * @return True if tracing is enabled, otherwise false.
	 */
	public static boolean isTracing() {
		return TRACING;
	}

	private static void log(final int level, final Object id, final String message, final Throwable throwable) {
		final boolean logging = isLogging(level);
		if (logging == false) {
			return;
		}
		final LogUtility instance = getInstance();
		final LogService log = instance.getLog();
		final String formattedMessage = instance.formatMessage(id, message);
		log.log(level, formattedMessage, throwable);
	}

	/**
	 * Log a <code>LogService.LOG_DEBUG</code> message.
	 *
	 * @param id
	 *            An object that uniquely identifies the source of the message
	 *            to logged.
	 * @param message
	 *            The message to log.
	 */
	public static void logDebug(final Object id, final String message) {
		logDebug(id, message, null);
	}

	/**
	 * Log a <code>LogService.LOG_DEBUG</code> level message.
	 *
	 * @param id
	 *            An object that identifies who is logging the message.
	 * @param message
	 *            The message that will be logged.
	 * @param throwable
	 *            An accompanying Throwable.
	 */
	public static void logDebug(final Object id, final String message, final Throwable throwable) {
		log(LogService.LOG_DEBUG, id, message, throwable);
	}

	/**
	 * Log a <code>LogService.LOG_DEBUG</code> message.
	 *
	 * @param message
	 *            The message to log.
	 */
	public static void logDebug(final String message) {
		logDebug(null, message, null);
	}

	/**
	 * Log a <code>LogService.LOG_DEBUG</code> level message.
	 *
	 * @param message
	 *            The message that will be logged.
	 * @param throwable
	 *            An accompanying Throwable.
	 */
	public static void logDebug(final String message, final Throwable throwable) {
		logDebug(null, message, throwable);
	}

	/**
	 * Log a <code>LogService.LOG_ERROR</code> message.
	 *
	 * @param id
	 *            An object that uniquely identifies the source of the message
	 *            to logged.
	 * @param message
	 *            The message to log.
	 */
	public static void logError(final Object id, final String message) {
		logError(id, message, null);
	}

	/**
	 * Log a <code>LogService.LOG_ERROR</code> level message.
	 *
	 * @param id
	 *            An object that identifies who is logging the message.
	 * @param message
	 *            The message that will be logged.
	 * @param throwable
	 *            An accompanying Throwable.
	 */
	public static void logError(final Object id, final String message, final Throwable throwable) {
		log(LogService.LOG_ERROR, id, message, throwable);
	}

	/**
	 * Log a <code>LogService.LOG_ERROR</code> message.
	 *
	 * @param message
	 *            The message to log.
	 */
	public static void logError(final String message) {
		logError(null, message, null);
	}

	/**
	 * Log a <code>LogService.LOG_ERROR</code> level message.
	 *
	 * @param message
	 *            The message that will be logged.
	 * @param throwable
	 *            An accompanying Throwable.
	 */
	public static void logError(final String message, final Throwable throwable) {
		logError(null, message, throwable);
	}

	/**
	 * Log a <code>LogService.LOG_INFO</code> message.
	 *
	 * @param id
	 *            An object that uniquely identifies the source of the message
	 *            to logged.
	 * @param message
	 *            The message to log.
	 */
	public static void logInfo(final Object id, final String message) {
		logInfo(id, message, null);
	}

	/**
	 * Log a <code>LogService.LOG_INFO</code> level message.
	 *
	 * @param id
	 *            An object that identifies who is logging the message.
	 * @param message
	 *            The message that will be logged.
	 * @param throwable
	 *            An accompanying Throwable.
	 */
	public static void logInfo(final Object id, final String message, final Throwable throwable) {
		log(LogService.LOG_INFO, id, message, throwable);
	}

	/**
	 * Log a <code>LogService.LOG_INFO</code> message.
	 *
	 * @param message
	 *            The message to log.
	 */
	public static void logInfo(final String message) {
		logInfo(null, message, null);
	}

	/**
	 * Log a <code>LogService.LOG_INFO</code> level message.
	 *
	 * @param message
	 *            The message that will be logged.
	 * @param throwable
	 *            An accompanying Throwable.
	 */
	public static void logInfo(final String message, final Throwable throwable) {
		logInfo(null, message, throwable);
	}

	/**
	 * Log a <code>LogService.DEBUG_INFO</code> level trace message.
	 *
	 * @param id
	 *            An object that identifies who is logging the message.
	 * @param message
	 *            The message that will be logged.
	 */
	public static void logTrace(final Object id, final String message) {
		logTrace(id, message, null);
	}

	/**
	 * Log a <code>LogService.DEBUG_INFO</code> level trace message.
	 *
	 * @param id
	 *            An object that identifies who is logging the message.
	 * @param message
	 *            The message that will be logged.
	 * @param throwable
	 *            An accompanying Throwable.
	 */
	public static void logTrace(final Object id, final String message, final Throwable throwable) {
		final boolean tracing = isTracing();
		if (tracing == false) {
			return; // Early return.
		}
		log(LogService.LOG_DEBUG, id, message, throwable);
	}

	/**
	 * Log a <code>LogService.DEBUG_INFO</code> level trace message.
	 *
	 * @param message
	 *            The message that will be logged.
	 */
	public static void logTrace(final String message) {
		logTrace(null, message, null);
	}

	/**
	 * Log a <code>LogService.DEBUG_INFO</code> level trace message.
	 *
	 * @param message
	 *            The message that will be logged.
	 * @param throwable
	 *            An accompanying Throwable.
	 */
	public static void logTrace(final String message, final Throwable throwable) {
		logTrace(null, message, throwable);
	}

	/**
	 * Log a <code>LogService.LOG_INFO</code> message.
	 *
	 * @param id
	 *            An object that uniquely identifies the source of the message
	 *            to logged.
	 * @param message
	 *            The message to log.
	 */
	public static void logWarning(final Object id, final String message) {
		logWarning(id, message, null);
	}

	/**
	 * Log a <code>LogService.LOG_WARNING</code> level message.
	 *
	 * @param id
	 *            An object that identifies who is logging the message.
	 * @param message
	 *            The message that will be logged.
	 * @param throwable
	 *            An accompanying Throwable.
	 */
	public static void logWarning(final Object id, final String message, final Throwable throwable) {
		log(LogService.LOG_WARNING, id, message, throwable);
	}

	/**
	 * Log a <code>LogService.LOG_WARNING</code> message.
	 *
	 * @param message
	 *            The message to log.
	 */
	public static void logWarning(final String message) {
		logWarning(null, message, null);
	}

	/**
	 * Log a <code>LogService.LOG_WARNING</code> level message.
	 *
	 * @param message
	 *            The message that will be logged.
	 * @param throwable
	 *            An accompanying Throwable.
	 */
	public static void logWarning(final String message, final Throwable throwable) {
		logWarning(null, message, throwable);
	}

	/**
	 * Set the current logging level.
	 *
	 * @param level
	 *            One of the <code>org.osgi.service.log.LogService</code>
	 *            constants.
	 */
	public static void setLoggingLevel(final int level) {
		final LogUtility instance = getInstance();
		instance.setLogLevel(level);
	}

	/**
	 * Set the tracing field to <code>true</code> or <code>false</code>.
	 *
	 * @param tracing
	 *            The value of <code>true</code> turns tracing on, and a value
	 *            of <code>false</code> turns tracing off.
	 */
	public static void setTracing(final boolean tracing) {
		TRACING = tracing;
	}

	private StringBuffer buffer;
	private IStatusIdFinder idFinder = null;
	private LogProxy proxy;

	public LogUtility() {
		super();
		setProxy(new LogProxy());
		setLogLevel(getInitialLogLevelValue());
		setBuffer(new StringBuffer(DEFAULT_BUFFER_SIZE));
	}

	public void bind(final LogService log) {
		getInstance().setLog(log);
	}

	private String findStatusId(final Object object) {
		return idFinder.getStatusId(object);
	}

	private String formatId(final Object object) {
		if (object instanceof String) { // $codepro.audit.disable
			// disallowInstanceof
			String name = (String) object;
			name = name.trim();
			return name; // Early return.
		} else if (object instanceof Class) { // $codepro.audit.disable
			// disallowInstanceof
			final Class clazz = (Class) object;
			String name = clazz.getName();
			name = getClassName(name);
			return name; // Early return.
		}
		String name = object.toString();
		name = name.trim();
		final Class clazz = object.getClass();
		final String className = clazz.getName();
		final boolean match = name.startsWith(className);
		if (match == true) {
			name = getClassName(className);
		}
		return name;
	}

	private String formatMessage(final Object id, final String message) {
		String formattedMessage = message;
		if (id != null) {
			final String formattedId = formatId(id);
			final StringBuffer buffer = getBuffer();
			// It is very important to synchronize on the buffer to ensure that
			// only one thread uses the buffer at a time. A buffer is used to
			// improve performance and reduce garbage generation.
			synchronized (buffer) {
				// Since the buffer reused, it must be emptied before it can be
				// used to format another message.
				final int count = buffer.length();
				buffer.delete(0, count);
				final int formattedIdLength = formattedId.length();
				if (formattedIdLength != 0) {
					buffer.append(formattedId);
					buffer.append(": ");
				}
				buffer.append(message);
				formattedMessage = buffer.toString();
			}
		}
		return formattedMessage;
	}

	private StringBuffer getBuffer() {
		return buffer;
	}

	private String getClassName(final String fullyQualifiedClassName) {
		final int index = fullyQualifiedClassName.lastIndexOf('.');
		final String name = fullyQualifiedClassName.substring(index + 1);
		return name;
	}

	/**
	 * Get the <code>org.osgi.service.log.LogService</code>.
	 *
	 * @return The log service.
	 */
	public LogService getLog() {
		final LogService log = getProxy();
		return log;
	}

	private int getLogLevel() {
		final LogProxy proxy = getProxy();
		final int logLevel = proxy.getLogLevel();
		return logLevel;
	}

	private LogProxy getProxy() {
		return proxy;
	}

	private void setBuffer(final StringBuffer buffer) {
		this.buffer = buffer;
	}

	/**
	 * Set the <code>org.osgi.service.log.LogService</code>.
	 *
	 * @param log
	 *            A log service.
	 */
	public void setLog(final LogService log) {
		final LogProxy proxy = getProxy();
		proxy.setLog(log);
	}

	private void setLogLevel(final int logLevel) {
		final LogProxy proxy = getProxy();
		proxy.setLogLevel(logLevel);
	}

	private void setProxy(final LogProxy proxy) {
		this.proxy = proxy;
	}

	public void setStatusIdFinder(final IStatusIdFinder value) {
		idFinder = value;
	}

	public void unbind(final LogService log) {
		final LogUtility utility = getInstance();
		final LogService service = ((LogProxy) utility.getLog()).getLog();
		if (service.equals(log)) {
			utility.setLog(null);
		}
	}

}